package com.eastmoney.emis.edda.infrastructure.eddi.service;

import com.eastmoney.emis.edda.config.HsbcEddaConfig;
import com.eastmoney.emis.edda.domain.entity.EddaBind;
import com.eastmoney.emis.edda.domain.entity.EddiInstruction;
import com.eastmoney.emis.edda.domain.event.EddiInstructionEvent;
import com.eastmoney.emis.edda.domain.event.EventPublisher;
import com.eastmoney.emis.edda.domain.repository.HsbcEddaRepository;
import com.eastmoney.emis.edda.domain.repository.HsbcEddiRepository;
import com.eastmoney.emis.edda.domain.service.IdService;
import com.eastmoney.emis.edda.domain.service.eddi.EddaDebitInstructionService;
import com.eastmoney.emis.edda.domain.service.eddi.EddiInstructionCommand;
import com.eastmoney.emis.edda.domain.valueobject.EddiOperation;
import com.eastmoney.emis.edda.domain.valueobject.EddiStatus;
import com.eastmoney.emis.edda.domain.valueobject.MoneyType;
import com.eastmoney.emis.edda.domain.valueobject.PaymentStatus;
import com.eastmoney.emis.edda.infrastructure.edda.service.HsbcApiErrorResolver;
import com.eastmoney.emis.edda.infrastructure.exception.EddaException;
import com.eastmoney.emis.edda.infrastructure.exception.EddaExceptionStatus;
import com.eastmoney.emis.edda.infrastructure.hsbc.HsbcEddaApi;
import com.eastmoney.emis.edda.infrastructure.hsbc.dto.AccountDto;
import com.eastmoney.emis.edda.infrastructure.hsbc.dto.InstructionReq;
import com.eastmoney.emis.edda.infrastructure.hsbc.dto.InstructionResp;
import com.eastmoney.emis.edda.task.TaskManagement;
import com.eastmoney.emis.edda.task.eddi.EddiTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.function.Predicate;

/**
 * @description: Description
 * @projectName: EDDA-HSBC
 * @package: com.eastmoney.emis.edda.infrastructure.eddi.service
 * @author: 汪程程
 * @date: 2022/10/11 10:14 AM
 * @version: 1.0
 */
@Service
public class EddaDebitInstructionServiceImpl implements EddaDebitInstructionService {
    @Autowired
    private HsbcEddaApi eddaApi;
    @Autowired
    private HsbcEddaConfig config;
    @Autowired
    private HsbcEddaRepository eddaRepository;
    @Autowired
    private HsbcEddiRepository eddiRepository;
    @Autowired
    private HsbcApiErrorResolver errorResolver;
    @Autowired
    private IdService idService;
    @Autowired
    private EventPublisher eventPublisher;

    @Autowired
    private TaskManagement taskManagement;

    @Override
    public void debitInstruction(EddiInstructionCommand command) {
        EddaBind eddaBind = eddaRepository.findEddaBindByClientIdAndBankAccount(
                command.getClientId(), command.getBankCode(), command.getBankAccount());
        // 绑定状态检查
        checkEddaBindStatus(eddaBind, r -> Objects.isNull(r) || !r.finished());
        // 存记录
        InstructionReq instructionReq = buildInstructionReq(command, eddaBind);
        EddiInstruction eddiInstruction = buildDebitInstruction(command, eddaBind, instructionReq);
        eddiRepository.saveDebitInstruction(eddiInstruction);
        // 调用银行
        InstructionResp instructionResp = eddaApi.debitInstruction(instructionReq);
        instructionResp.onSuccess(r -> {
            changeEddiByPaymentStatus(instructionResp.PaymentInformationStatus, eddiInstruction);
            eddiInstruction.setTransactionId(instructionResp.TransactionIdentification);
        }).onFail(r -> {
            eddiInstruction.setStatus(EddiStatus.BANK_PROCESS_FAIL);
        });
        eddiRepository.saveDebitInstruction(eddiInstruction);
        // 记录流水
        publishEddiInstructionEvent(eddiInstruction, instructionResp);
        // 处理异常
        errorResolver.resolveAndTrow(instructionResp);

    }

    @Override
    public void debitInstructionOffline(EddiInstructionCommand command) {
        EddaBind eddaBind = eddaRepository.findEddaBindByClientIdAndBankAccount(
                command.getClientId(), command.getBankCode(), command.getBankAccount());
        // 绑定状态检查
        checkEddaBindStatus(eddaBind, r -> Objects.isNull(r) || !r.finished());
        // 存记录
        EddiInstruction eddiInstruction = buildEddiInstructionOffline(eddaBind, command.getInstructedAmount());
        eddiRepository.saveDebitInstruction(eddiInstruction);
        publishEddiInstructionEvent(eddiInstruction, null);
    }

    @Override
    public void debitInstructionAsync(EddiInstructionCommand command) {
        EddaBind eddaBind = eddaRepository.findEddaBindByClientIdAndBankAccount(
                command.getClientId(), command.getBankCode(), command.getBankAccount());
        checkEddaBindStatus(eddaBind, r -> Objects.isNull(r) || !r.finished());
        EddiInstruction eddiInstruction = buildEddiInstructionOffline(eddaBind, command.getInstructedAmount());
        eddiRepository.saveDebitInstruction(eddiInstruction);
        publishEddiInstructionEvent(eddiInstruction, null);
        //taskManagement.addTask(new EddiTask(eddiInstruction));
    }

    private EddiInstruction buildEddiInstructionOffline(EddaBind eddaBind, String occurBalance) {
        EddiInstruction eddiInstruction = new EddiInstruction(eddaBind);
        eddiInstruction.setApplySerialNo(idService.generateId());
        eddiInstruction.setOccurBalance(new BigDecimal(occurBalance));
        return eddiInstruction;
    }

    private void changeEddiByPaymentStatus(String paymentStatus, EddiInstruction eddiInstruction) {
        EddiStatus eddiStatus = PaymentStatus.parse(paymentStatus).transferToEddiStatus();
        eddiInstruction.setStatus(eddiStatus);
    }

    private void publishEddiInstructionEvent(EddiInstruction eddiInstruction, InstructionResp resp) {
        EddiInstructionEvent event = new EddiInstructionEvent(eddiInstruction);
        event.setOperation(EddiOperation.APPLY.getValue());
        if (resp != null) {
            event.setResult(resp.result());
            event.setApplyReq(resp.context().getReqPayload());
            event.setApplyRes(resp.context().getRespPayload());
            event.setErrorReason(resp.rejectReason());
        }
        eventPublisher.publish(event);
    }

    private EddiInstruction buildDebitInstruction(EddiInstructionCommand command, EddaBind eddaBind, InstructionReq instructionReq) {
        EddiInstruction eddiInstruction = new EddiInstruction(eddaBind);
        eddiInstruction.setApplySerialNo(instructionReq.MerchantInstructionIdentification);
        eddiInstruction.setMoneyType(MoneyType.parse(instructionReq.InstructedAmountCurrency).getId().toString());
        eddiInstruction.setOccurBalance(new BigDecimal(command.getInstructedAmount()));
        eddiInstruction.setStatus(EddiStatus.BANK_PROCESS);
        return eddiInstruction;
    }

    private InstructionReq buildInstructionReq(EddiInstructionCommand command, EddaBind eddaBind) {
        InstructionReq instructionReq = new InstructionReq();
        instructionReq.MerchantInstructionIdentification = idService.generateId();
        instructionReq.MandateIdentification = eddaBind.getMandateId();
        instructionReq.CreditorAccount = new AccountDto(config.getCreditorAccount());
        instructionReq.SmsLanguageCode = command.getLanguage();
        instructionReq.InstructedAmount = command.getInstructedAmount();
        return instructionReq;
    }

    private void checkEddaBindStatus(EddaBind eddaBind, Predicate<EddaBind> predicate) {
        if (predicate.test(eddaBind)) {
            throw new EddaException(EddaExceptionStatus.INVALID_EDDA_BIND);
        }
    }
}
